﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Kinect;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using Blake.NUI.WPF.Utility;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;

namespace KinectGhostHunter
{
    class BooController : Grid
    {
        #region Fields
        Random random = new Random();
        Image kinectImage;
        Canvas canvas;

        int numBoosCreated = 0;

        Dictionary<int, List<BooImage>> trackingBoos = new Dictionary<int, List<BooImage>>();
        ScaleTransform flipTransform;

        #endregion

        #region Properties

        public MediaElement BooLaughElement { get; set; }

        public KinectSensor Kinect { get; set; }

        public ImageSource KinectImageSource
        {
            get
            {
                return kinectImage.Source;
            }
            set
            {
                kinectImage.Source = value;
            }
        }

        #endregion

        #region Constructors

        public BooController()
        {
            kinectImage = new Image();
            kinectImage.Stretch = System.Windows.Media.Stretch.Uniform;
            this.Loaded += new System.Windows.RoutedEventHandler(BooController_Loaded);
        }

        void BooController_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Children.Add(kinectImage);
            canvas = new Canvas();
            this.Children.Add(canvas);

            flipTransform = new ScaleTransform(-1, 1);
            flipTransform.Freeze();
        }

        #endregion

        #region Public Methods

        public void UpdateBoos(Skeleton[] skeletons)
        {
            if (Kinect == null)
                return;
            canvas.Width = kinectImage.ActualWidth;
            canvas.Height = kinectImage.ActualHeight;

            List<int> allPlayers = new List<int>();
            allPlayers.AddRange(trackingBoos.Keys.ToList());

            for (int i = 0; i < skeletons.Length; i++)
            {
                var skeleton = skeletons[i];
                if (skeleton.TrackingState != SkeletonTrackingState.NotTracked)
                {
                    UpdateBooHitting(skeleton);
                    ProcessSkeleton(skeleton, i);
                    if (allPlayers.Contains(i))
                    {
                        allPlayers.Remove(i);
                    }
                }
            }

            foreach (var oldPlayer in allPlayers)
            {
                var booList = trackingBoos[oldPlayer];
                if (booList != null)
                {
                    foreach (var boo in booList)
                    {
                        if (canvas.Children.Contains(boo))
                        {
                            canvas.Children.Remove(boo);
                        }
                    }
                }
                trackingBoos.Remove(oldPlayer);
            }
        }

        #endregion

        #region Private Methods

        void PlayBooLaugh()
        {
            BooLaughElement.Stop();
            BooLaughElement.Play();
        }

        private void UpdateBooHitting(Skeleton skeleton)
        {
            foreach (var booList in trackingBoos.Values)
            {
                foreach (var boo in booList)
                {
                    if (boo.Status != BooStatus.Dead)
                    {
                        bool hitting = IsUserHittingBoo(boo, skeleton);

                        if (hitting)
                        {
                            boo.Status = BooStatus.Dead;
                            var velocity = boo.BooVelocity;
                            velocity.Y += .5f;
                            velocity.X *= 30;
                            boo.BooVelocity = velocity;
                            UpdateBoo(boo);
                        }
                    }
                }
            }
        }

        private bool IsUserHittingBoo(BooImage boo, Skeleton skeleton)
        {
            if (skeleton == null || skeleton.TrackingState != SkeletonTrackingState.Tracked)
                return false;

            var handLeft = skeleton.Joints[JointType.HandLeft];
            var handRight = skeleton.Joints[JointType.HandRight];

            if (handLeft.TrackingState != JointTrackingState.Tracked ||
                handRight.TrackingState != JointTrackingState.Tracked)
            {
                return false;
            }

            var posLeft = handLeft.Position;
            var posRight = handRight.Position;
            var booPosition = boo.BooPosition;

            var leftDist = Math.Sqrt(Math.Pow(posLeft.X - booPosition.X, 2) +
                                     Math.Pow(posLeft.Y - booPosition.Y, 2));

            var rightDist = Math.Sqrt(Math.Pow(posRight.X - booPosition.X, 2) +
                                      Math.Pow(posRight.Y - booPosition.Y, 2));

            if (leftDist < .3 ||
                rightDist < .3)
            {
                return true;
            }
            return false;
        }

        private void ProcessSkeleton(Skeleton skeleton, int playerIndex)
        {
            if (skeleton == null || skeleton.TrackingState != SkeletonTrackingState.Tracked)
                return;
            List<BooImage> booList = null;

            var userPosition = skeleton.Position;

            float defaultBooOffset = -2;
            double angle = GetUserFacingAngle(skeleton);
            if (double.IsNaN(angle))
            {
                return;
            }
            else if (angle >= 0)
            {
                defaultBooOffset = -2f;
            }
            else if (angle < 0)
            {
                defaultBooOffset = 2f;
            }

            if (!trackingBoos.ContainsKey(playerIndex) ||
                trackingBoos[playerIndex] == null ||
                trackingBoos[playerIndex].Count == 0)
            {
                bool createBooSwam = numBoosCreated >= 3;
                int numBoosToCreate = 1;
                //createBooSwam = true;
                if (createBooSwam)
                {
                    numBoosCreated = 0;
                    numBoosToCreate = 20;
                }
                else
                {
                    numBoosCreated++;
                }
                PlayBooLaugh();
                for (int i = 0; i < numBoosToCreate; i++)
                {
                    var newBooPosition = userPosition;
                    float rand = (float)random.NextDouble() - 0.25f;

                    newBooPosition.Y += 0.5f + rand;

                    if (createBooSwam)
                    {
                        newBooPosition.X += (2f + i) * (i % 2 == 0 ? 1 : -1);
                    }
                    else
                    {
                        newBooPosition.X += defaultBooOffset;
                    }

                    var boo = CreateBoo(playerIndex);

                    boo.BooPosition = newBooPosition;
                    boo.TargetUserPositionOffsetY = 0.5f;
                    if (createBooSwam)
                    {
                        boo.IsBooSwarm = true;
                        boo.TargetUserPositionOffsetY = (float)random.NextDouble()*0.75f - 0.25f;
                    }
                    UpdateBoo(boo, false);
                }
            }

            booList = trackingBoos[playerIndex];
            List<BooImage> boosToRemove = new List<BooImage>();
            foreach (var boo in booList)
            {
                if (boo.Status != BooStatus.Dead)
                {
                    UpdateBooChasing(skeleton, boo, userPosition);
                }
                else
                {
                    bool removeBoo = UpdateBooFalling(playerIndex, boo);
                    if (removeBoo)
                    {
                        boosToRemove.Add(boo);
                    }
                }
            }

            foreach (var boo in boosToRemove)
            {
                if (booList.Contains(boo))
                {
                    booList.Remove(boo);
                }
                if (canvas.Children.Contains(boo))
                {
                    canvas.Children.Remove(boo);
                }
            }
            if (booList.Count == 0)
            {
                trackingBoos.Remove(playerIndex);
            }
        }

        private bool UpdateBooFalling(int playerIndex, BooImage boo)
        {
            var booPosition = boo.BooPosition;
            var booVelocity = boo.BooVelocity;
            booVelocity.Y -= 0.05f;
            booPosition.X += booVelocity.X / 10;
            booPosition.Y += booVelocity.Y / 10;
            boo.BooPosition = booPosition;
            boo.BooVelocity = booVelocity;

            bool removeBoo = false;
            if (booPosition.Y < -3)
            {
                boo.Status = BooStatus.Invisible;
                if (trackingBoos.ContainsKey(playerIndex))
                {
                    removeBoo = true;
                }
            }
            UpdateBoo(boo);
            return removeBoo;
        }

        private void UpdateBooChasing(Skeleton skeleton, BooImage boo, SkeletonPoint userPosition)
        {
            var booPosition = boo.BooPosition;
            var vector = new SkeletonPoint()
            {
                X = userPosition.X - booPosition.X,
                Y = userPosition.Y + boo.TargetUserPositionOffsetY - booPosition.Y,
                Z = userPosition.Z - booPosition.Z
            };

            double len = Math.Sqrt(vector.X * vector.X +
                                   vector.Y * vector.Y +
                                   vector.Z * vector.Z);

            float factor = 0.01f;

            double userAngle = GetUserFacingAngle(skeleton);
            if (double.IsNaN(userAngle))
            {
                userAngle = boo.lastUserAngle;
            }
            else
            {
                boo.lastUserAngle = userAngle;
            }

            if (vector.X > 0 && userAngle < -20 && !boo.IsBooSwarm)
            {
                boo.Status = BooStatus.Hiding;
                factor = 0;
            }
            else if (vector.X < 0 && userAngle > 20 && !boo.IsBooSwarm)
            {
                boo.Status = BooStatus.Hiding;
                factor = 0;
            }
            else if (len < 0.3 && (userAngle >= -20 || userAngle <= 20) && !boo.IsBooSwarm)
            {
                boo.Status = BooStatus.Hiding;
                factor = 0;
            }
            else
            {
                if (len < 0.3)
                {
                    factor = 0;
                    boo.Status = BooStatus.ChasingClose;
                    //boo.Status = BooStatus.Invisible;
                }
                else if (len < 0.5)
                {
                    factor = 0.01f;
                    boo.Status = BooStatus.ChasingClose;
                }
                else if (len < .75)
                {
                    factor = 0.02f;
                    boo.Status = BooStatus.Chasing;
                }
                else
                {
                    factor = 0.04f;
                    boo.Status = BooStatus.Chasing;
                }
            }

            var updatedPosition = new SkeletonPoint()
            {
                X = booPosition.X + vector.X * factor,
                Y = booPosition.Y + vector.Y * factor,
                Z = booPosition.Z + vector.Z * factor
            };

            if (updatedPosition.X - booPosition.X >= 0)
            {
                boo.RenderTransform = null;
            }
            else
            {
                boo.RenderTransform = flipTransform;
            }

            boo.BooVelocity = new SkeletonPoint()
                {
                    X = booPosition.X - updatedPosition.X,
                    Y = booPosition.Y - updatedPosition.Y,
                    Z = booPosition.Z - updatedPosition.Z
                };

            //if (Math.Abs(boo.BooPosition.X) >= 2 &&
            //    Math.Abs(updatedPosition.X) < 2)
            //{
            //    PlayBooLaugh();
            //}
            boo.BooPosition = updatedPosition;

            UpdateBoo(boo);
        }

        private double GetUserFacingAngle(Skeleton skeleton)
        {
            var shoulderLeft = skeleton.Joints[JointType.ShoulderLeft];
            var shoulderRight = skeleton.Joints[JointType.ShoulderRight];

            if (shoulderLeft.TrackingState != JointTrackingState.Tracked &&
                shoulderRight.TrackingState != JointTrackingState.Tracked)
            {
                return double.NaN;
            }
            else if (shoulderLeft.TrackingState != JointTrackingState.Tracked)
            {
                return -90;
            }
            else if (shoulderRight.TrackingState != JointTrackingState.Tracked)
            {
                return 90;
            }
            var leftPos = shoulderLeft.Position;
            var rightPos = shoulderRight.Position;
            var angle = MathUtility.Get2DAngle(new System.Windows.Vector(rightPos.X - leftPos.X, leftPos.Z - rightPos.Z),
                                               new System.Windows.Vector(1, 0));
            return angle;
        }

        private BooImage CreateBoo(int playerIndex)
        {
            var boo = new BooImage();

            List<BooImage> booList = null;
            if (trackingBoos.ContainsKey(playerIndex))
            {
                booList = trackingBoos[playerIndex];
            }
            if (booList == null)
            {
                booList = new List<BooImage>();
                trackingBoos[playerIndex] = booList;
            }
            booList.Add(boo);
            boo.Status = BooStatus.Searching;
            boo.RenderTransformOrigin = new System.Windows.Point(0.5, 0.5);
            canvas.Children.Add(boo);
            return boo;
        }

        private void UpdateBoo(BooImage boo, bool useTransitions = true)
        {
            var position = boo.BooPosition;

            var mapper = Kinect.CoordinateMapper;
            var colorPoint = mapper.MapSkeletonPointToColorPoint(position, Kinect.ColorStream.Format);

            boo.Width = MathUtility.MapValue(position.Z, 1, 4, 300, 50);
            boo.Height = boo.Width * .872;

            double booX = (colorPoint.X / (double)Kinect.ColorStream.FrameWidth) * kinectImage.ActualWidth - boo.Width / 2;
            double booY = (colorPoint.Y / (double)Kinect.ColorStream.FrameHeight) * kinectImage.ActualHeight - boo.Height / 2;

            if (useTransitions)
            {
                AnimateUtility.AnimateElementDouble(boo, Canvas.LeftProperty, booX, 0, .2);
                AnimateUtility.AnimateElementDouble(boo, Canvas.TopProperty, booY, 0, .2);
            }
            else
            {
                Canvas.SetLeft(boo, booX);
                Canvas.SetTop(boo, booY);
            }
        }

        #endregion
    }
}
